package jsc.base;


/**
 * @author Marc Gaëtano
 * 
 * The class <tt>WordEnumerator</tt> provides iterators to enumerate
 * words from a text (a String).<p>
 *
 * A <tt>WordEnumerator</tt> can be seen as a word stream, each word
 * of the stream being an english word formed by letters.<p>
 *
 * For example, this code prints out all the words occuring in the
 * String <tt>someText</tt>:<p>
 *
 * <pre>
 *      String someText = ...;
 *      WordEnumerator we = new WordEnumerator(someText);
 *
 *      while ( ! we.noMoreWord() ) {
 *
 *          int offset = we.offset(); // the index in someText of the 
 *                                    // first letter of the current word
 *          int length = we.length(); // the length of the current word
 *
 *          // print the current word
 *          String word = someText.substring(offset,offset+length);
 *          System.out.println(word);
 *
 *          we.move(); // move to the next word or to the end of
 *                     // enumeration if there is no more word.
 *      }
 * </pre>
 */
public class WordEnumerator {

    protected String text; // the text to enumerate the words from
    private int max;       // the length of the text
    private int c;         // a character cursor
    private int offset;    // the index of the first letter of the current word
    private int length;    // the length of the current word
    private boolean nmw;   // nmw <==> there is no more word in the enumerator

    /**
     * Create a <tt>WordEnumerator</tt>.
     * @param text the text to spell-check.
     */
    public WordEnumerator(String text) {
        this.text = text;
        this.max = text.length();
        this.c = 0;
        this.offset = 0;
        this.length = 0;
        this.nmw = false;
        nextWord();
    }

    /**
     * Test if there is a current word in this
     * <tt>WordEnumerator</tt>.
     * @return <code>true</code> if there is no more word
     * in this <tt>WordEnumerator</tt>, <code>false</code>
     * otherwise.
     */
    public boolean noMoreWord() {
        return nmw;
    }

    /**
     * Return the index in the text (a <tt>String</tt>)
     * of the first letter of the current word.
     * @throws JSCerror if there is no more word
     * in this <tt>WordEnumerator</tt>.
     */
    public int offset() throws JSCerror {
        if ( nmw ) {
            throw new JSCerror("cannot offset: end of enumeration");
        }
        return offset;
    }

    /**
     * Return the length of the current word.
     * @throws JSCerror if there is no more word
     * in this <tt>WordEnumerator</tt>.
     */
    public int length() throws JSCerror {
        if ( nmw ) {
            throw new JSCerror("cannot length: end of enumeration");
        }
        return length;
    }

    /**
     * Move to the next word in the text or
     * to the end of enumeration if there is no more
     * word.
     * @throws JSCerror if there is no more word
     * in this <tt>WordEnumerator</tt>.
     */
    public void move() throws JSCerror {
        if ( nmw ) {
            throw new JSCerror("cannot move: end of enumeration");
        }
        nextWord();
    }

    // move to the next word or to the end of enumration
    // there is no more word in this enumeration
    private void nextWord() {
        while ( c < max && isDelimiter(c) ) {
            c++;
        }
        if ( c < max ) {
            offset = c;
            while ( c < max && isChar(c) ) {
                c++;
            }
            length = c - offset;
            if ( text.charAt(c-1) == '\'' ) {
                length--;
            }
        }
        else {
            nmw = true;
        }
    }

    // test if c is a delimiter    
    private boolean isDelimiter(int c) {
        return ! isCharacter(c);
    }

    // test if c is a letter
    private boolean isCharacter(int c) {
        char cur = text.charAt(c);
        return ( 'a' <= cur && cur <= 'z' ) || ( 'A' <= cur && cur <= 'Z' );
    }

    // test if c is a letter or the character '\'' (quote)
    private boolean isChar(int c) {
        return isCharacter(c) || text.charAt(c) == '\'';
    }
    
}
